package appTempl

import (
	"errors"
	"fmt"
	"github.com/astaxie/beego/orm"
	"github.com/golang/glog"
	"github.com/google/uuid"
	"reflect"
	"strings"
	"time"

	authapi "eccgateway/pkg/api/auth"
	"eccgateway/pkg/api/dataselect"
	"eccgateway/pkg/db/appTempl"
	dbAppTempl "eccgateway/pkg/db/appTempl"
)

var (
	TemplAlreadyPublic  = errors.New("template already public")
	TemplstatusError    = errors.New("statusError")
	TemplAlreadyPrivate = errors.New("template already private")
)

type Templ struct {
	Uuid          string    `json:"uuid"`
	Name          string    `json:"name"`
	Arch          string    `json:"arch"`
	UserId        string    `json:"user_id"`
	Creator       string    `json:"creator"`
	Template      string    `json:"template"`
	Version       string    `json:"version"`
	Privacy       string    `json:"privacy"`
	Auditor       string    `json:"auditor,omitempty"`
	AuditResult   string    `json:"auditResult,omitempty"`
	AuditTime     time.Time `json:"auditTime,omitempty"`
	CreateTime    time.Time `json:"createTime"`
	Status        int       `json:"status"`
	Modifier      string    `json:"modifier,omitempty"`
	ModifyTime    time.Time `json:"modify_time"`
	Desc          string    `json:"desc"`
	Log           string    `json:"log,omitempty"`
	ImageUrl      string    `json:"image_url"`
	Industry      string    `json:"industry"`
	Type          string    `json:"type"`
	Title         string    `json:"title"`
	Kind          string    `json:"kind"`
	DownloadCount int       `json:"downloadCount"`
	StarCount     int       `json:"starCount"`
}
type StarTempl struct {
	Uuid       string    `json:"id"`
	TemplId    string    `json:"templ_id"`
	UserId     string    `json:"user_id"`
	CreateTime time.Time `json:"createTime" `
}

type List struct {
	Count int64   `json:"count"`
	Items []Templ `json:",omitempty"`
}

type Response struct {
	Name    string `json:"name"`
	Auditor string `json:"auditor"`
	Version string `json:"version"`
	Log     string `json:"log"`
}

type Log struct {
	ID          int       `json:"id"`
	Version     string    `json:"version"`
	TemplUuid   string    `json:"templ_uuid"`
	Name        string    `json:"name"`
	Auditor     string    `json:"auditor"`
	AuditTime   time.Time `json:"audit_time"`
	AuditResult string    `json:"audit_result"`
	Desc        string    `json:"desc"`
}

func NewTempl() Templ {
	return Templ{
		Uuid:          "",
		Name:          "",
		Arch:          "",
		UserId:        "",
		Creator:       "",
		Template:      "",
		Version:       "",
		Privacy:       "0",
		Auditor:       "",
		AuditResult:   "",
		AuditTime:     time.Time{},
		CreateTime:    time.Time{},
		Status:        0,
		Modifier:      "",
		ModifyTime:    time.Time{},
		Desc:          "",
		Log:           "",
		DownloadCount: 0,
		StarCount:     0,
	}
}

func (t *Templ) Create(userInfo authapi.RespToken, templ Templ) (Templ, error) {
	templ.Uuid = uuid.New().String()
	templ.Creator = userInfo.Name
	templ.CreateTime = time.Now()
	templ.ModifyTime = time.Now()
	templ.AuditTime = time.Now()
	templ.Modifier = userInfo.Name
	templ.Status = 1
	templ.UserId = userInfo.UserID
	dbTempl := transformAPI2DB(templ)
	createAppTempl, err := appTempl.CreateAppTempl(orm.NewOrm(), dbTempl)
	if err != nil {
		glog.Errorf("create appTempl [%v] for user[%v/%v] in db failed, err: %v", templ.Name, userInfo.Name, userInfo.UserID, err)
		return Templ{}, err
	}
	return transformDB2API(createAppTempl), nil
}

func (t *Templ) UpdateTempl(userInfo authapi.RespToken, templ Templ) error {
	dbTempl, err := appTempl.GetTemplByUuid(orm.NewOrm(), templ.Uuid)
	if err != nil {
		glog.Errorf("can't find template for uuid %s", templ.Uuid)
		return err
	}

	userHighestAuthority := GetHighestAuthority(userInfo)
	var adminAndUseGroupIdEqual bool
	adminAndUseGroupIdEqual, err = appTempl.CheckUserGroupIsEqual(userInfo.UserID, dbTempl.UserId)
	if err != nil {
		return err
	}
	//1 stand for superUser, 2 admin user authority
	if userInfo.UserID == dbTempl.UserId || userHighestAuthority == 1 || (userHighestAuthority == 2 && (adminAndUseGroupIdEqual)) {
		dbTempl.Modifier = userInfo.Name
		dbTempl.ModifyTime = time.Now()
		dbTempl.Template = templ.Template
		updateFiledSlice := []string{"Name", "Version", "Type", "Arch", "Desc", "Title", "Industry", "ImageUrl"}
		tval := reflect.ValueOf(templ)
		dbtval := reflect.ValueOf(&dbTempl).Elem()
		for _, field := range updateFiledSlice {
			val := tval.FieldByName(field).String()
			if val != "" {
				newVal := reflect.ValueOf(val).String()
				dbtval.FieldByName(field).SetString(newVal)
			}
		}
		err := appTempl.UpdateTempl(orm.NewOrm(), dbTempl)
		return err
	}
	return nil
}

func (t *Templ) ListTemplByUserId(userIDs []string, query *dataselect.DataSelectQuery) ([]Templ, int64, error) {
	templs, count, err := appTempl.ListTemplAll(orm.NewOrm(), userIDs, query)
	if err == orm.ErrNoRows {
		glog.Infof("can't list Template for user[%v] in db", strings.Join(userIDs, ","))
		return []Templ{}, 0, nil
	}
	if err != nil {
		glog.Errorf("can't list batch work for user[%v] in db, err: %v", strings.Join(userIDs, ","), err)
		return []Templ{}, 0, err
	}
	return transformsDB2API(templs), count, nil
}

func transformsDB2API(dbTempls []dbAppTempl.Templ) []Templ {
	res := make([]Templ, 0, len(dbTempls))
	for _, v := range dbTempls {
		res = append(res, transformDB2API(v))
	}
	return res
}

func (t *Templ) GetTempl(userInfo authapi.RespToken, uuid string) (Templ, error) {
	templ, err := appTempl.GetTemplByUuid(orm.NewOrm(), uuid)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", uuid)
		return Templ{}, err
	}
	ok, err := t.CheckOperationAuthority(userInfo, uuid)
	if !ok {
		return Templ{}, err
	}
	return transformDB2API(templ), nil
}

func (t *Templ) GetPubTempls(query *dataselect.DataSelectQuery) ([]Templ, int64, error) {
	templs, count, err := appTempl.GetPubTempList(orm.NewOrm(), query)
	if err == orm.ErrNoRows {
		glog.Infof("list public Template error for %s", err.Error())
		return []Templ{}, 0, nil
	}
	if err != nil {
		glog.Errorf("list public Template error for %s", err.Error())
		return []Templ{}, 0, err
	}
	return transformsDB2API(templs), count, nil
}

func (t *Templ) DeleteTempl(userInfo authapi.RespToken, uuid string) error {
	templ, err := appTempl.GetTemplByUuid(orm.NewOrm(), uuid)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", uuid)
		return err
	}
	ok, err := t.CheckOperationAuthority(userInfo, uuid)
	if !ok {
		return err
	}
	err = appTempl.DeleteTempl(orm.NewOrm(), templ)
	return err
}

func (t *Templ) CheckOperationAuthority(userInfo authapi.RespToken, tempUuid string) (bool, error) {
	templ, err := appTempl.GetTemplByUuid(orm.NewOrm(), tempUuid)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", tempUuid)
		return false, err
	}
	userHighestAuthority := GetHighestAuthority(userInfo)
	var adminAndUseGroupIdEqual bool
	adminAndUseGroupIdEqual, err = appTempl.CheckUserGroupIsEqual(userInfo.UserID, templ.UserId)
	if err != nil {
		glog.Errorf("operation not permitted")
		return false, err
	}
	if userInfo.UserID == templ.UserId || userHighestAuthority == 1 || (userHighestAuthority == 2 && (adminAndUseGroupIdEqual)) {
		return true, nil
	}
	return false, err
}

func (t *Templ) SetTemplPublic(userInfo authapi.RespToken, uuid string) (Templ, error) {
	templ, err := appTempl.GetTemplByUuid(orm.NewOrm(), uuid)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", uuid)
		return Templ{}, err
	}
	ok, err := t.CheckOperationAuthority(userInfo, uuid)
	if !ok {
		return Templ{}, err
	}
	templ, err = appTempl.SetTemplPublic(orm.NewOrm(), uuid)
	return transformDB2API(templ), nil
}

func (t *Templ) SetTemplPrivate(userInfo authapi.RespToken, uuid string) (Templ, error) {
	o := orm.NewOrm()
	templ, err := appTempl.GetTemplByUuid(o, uuid)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", uuid)
		return Templ{}, err
	}
	ok, err := t.CheckOperationAuthority(userInfo, uuid)
	if !ok {
		return Templ{}, err
	}
	if templ.Status != 4 {
		return Templ{}, TemplAlreadyPrivate
	}
	templ.Status = 3
	templ.Privacy = "0"
	templ.Modifier = userInfo.Name
	templ.StarCount = 0
	templ.DownloadCount = 0
	templ.ModifyTime = time.Now()
	o.Begin()
	err1 := appTempl.UpdateTempl(o, templ)
	if err1 != nil {
		fmt.Errorf("update temp is failed, %s", err1)
		o.Rollback()
		return transformDB2API(templ), err1
	}
	err2 := appTempl.DeleteStarTempl(o, templ.Uuid)
	if err2 != nil {
		fmt.Errorf("delete starTemp is failed, %s", err2)
		o.Rollback()
		return transformDB2API(templ), err2
	} else {
		o.Commit()
	}
	return transformDB2API(templ), nil
}

func (t *Templ) AuditApprove(userInfo authapi.RespToken, te Templ) (appTempl.Templ, error) {
	templ, err := appTempl.GetTemplByUuid(orm.NewOrm(), te.Uuid)
	log := appTempl.Log{}
	if err != nil {
		return templ, fmt.Errorf("no template found")
	}
	if templ.Status == 4 {
		return templ, TemplAlreadyPublic
	}
	if templ.Status != 2 {
		return templ, TemplstatusError
	}
	if te.AuditResult == "1" {
		templ.Status = 5
	} else {
		templ.Status = 4
		templ.Privacy = "1"
		templ.AuditResult = "0"
	}
	templ.Auditor = userInfo.Name
	templ.Log = templ.Desc
	templ.AuditTime = time.Now()
	templ.AuditResult = te.AuditResult
	log.Desc = te.Desc
	log.Name = templ.Name
	log.Auditor = userInfo.Name
	log.AuditTime = time.Now()
	log.TemplUuid = templ.Uuid
	log.Version = templ.Version
	log.AuditResult = "0"
	err = appTempl.InsertLog(orm.NewOrm(), log)
	if err != nil {
		glog.Errorf("template audit log insert error， %v", err)
	}
	err = appTempl.UpdateTempl(orm.NewOrm(), templ)
	return templ, err
}

func (t *Templ) GetLogs(templName string, version string) ([]Log, error) {
	logs, err := appTempl.GetLogs(orm.NewOrm(), templName, version)
	if err != nil {
		return []Log{}, nil
	}
	apiLogs := make([]Log, 0, len(logs))
	for _, log := range logs {
		apiLogs = append(apiLogs, Log{
			ID:          log.ID,
			Version:     log.Version,
			TemplUuid:   log.TemplUuid,
			Name:        log.Name,
			Auditor:     log.Auditor,
			AuditTime:   log.AuditTime,
			AuditResult: log.AuditResult,
			Desc:        log.Desc,
		})
	}
	return apiLogs, err
}

func transformDB2API(t dbAppTempl.Templ) Templ {
	return Templ{
		Uuid:          t.Uuid,
		Desc:          t.Desc,
		Creator:       t.Creator,
		Arch:          t.Arch,
		UserId:        t.UserId,
		Name:          t.Name,
		Template:      t.Template,
		Version:       t.Version,
		Privacy:       t.Privacy,
		Auditor:       t.Auditor,
		AuditResult:   t.AuditResult,
		AuditTime:     t.AuditTime,
		CreateTime:    t.CreateTime,
		Status:        t.Status,
		Modifier:      t.Modifier,
		ModifyTime:    t.ModifyTime,
		Log:           "",
		Industry:      t.Industry,
		Type:          t.Type,
		ImageUrl:      t.ImageUrl,
		Title:         t.Title,
		Kind:          t.Kind,
		DownloadCount: t.DownloadCount,
		StarCount:     t.StarCount,
	}
}

func transformAPI2DB(t Templ) dbAppTempl.Templ {
	return dbAppTempl.Templ{
		Uuid:          t.Uuid,
		Creator:       t.Creator,
		Arch:          t.Arch,
		Name:          t.Name,
		Template:      t.Template,
		Version:       t.Version,
		Privacy:       t.Privacy,
		Auditor:       t.Auditor,
		AuditResult:   t.AuditResult,
		AuditTime:     t.AuditTime,
		UserId:        t.UserId,
		CreateTime:    t.CreateTime,
		Status:        t.Status,
		Desc:          t.Desc,
		Modifier:      t.Modifier,
		ModifyTime:    t.ModifyTime,
		Industry:      t.Industry,
		Type:          t.Type,
		ImageUrl:      t.ImageUrl,
		Title:         t.Title,
		Kind:          t.Kind,
		DownloadCount: t.DownloadCount,
		StarCount:     t.StarCount,
	}
}

func GetHighestAuthority(useInfo authapi.RespToken) int {
	var role_id = 10
	for _, role := range useInfo.Role {
		if role.ID < role_id {
			role_id = role.ID
		}
	}
	return role_id
}

func (t *Templ) ListStarTemplByUserId(userIDs []string, query *dataselect.DataSelectQuery) ([]Templ, int64, error) {
	templs, count, err := appTempl.ListStarTemplAll(orm.NewOrm(), userIDs, query)
	if err == orm.ErrNoRows {
		glog.Infof("can't list Template for user[%v] in db", strings.Join(userIDs, ","))
		return []Templ{}, 0, nil
	}
	if err != nil {
		glog.Errorf("can't list Template for user[%v] in db, err: %v", strings.Join(userIDs, ","), err)
		return []Templ{}, 0, err
	}
	return transformsStarTempDB2API(templs), count, nil
}
func transformsStarTempDB2API(dbStarTempls []dbAppTempl.StarTempl) []Templ {
	res := make([]Templ, 0, len(dbStarTempls))
	for _, v := range dbStarTempls {
		res = append(res, transformDB2APIForTemplInStar(v))
	}
	return res
}

func (t *Templ) UpdateTemplDownLoad(userInfo authapi.RespToken, uuid string) (Templ, error) {
	templ, err := appTempl.GetTemplByUuid(orm.NewOrm(), uuid)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", uuid)
		return Templ{}, err
	}

	if templ.Privacy != "1" {
		return Templ{}, fmt.Errorf("update starTempl [%v] for user[%v/%v] by download failed, err: template must be public", templ.Uuid, userInfo.Name, userInfo.UserID)
	}
	templ, err = appTempl.UpdateTemplDownLoad(orm.NewOrm(), uuid)
	return transformDB2API(templ), nil
}
func (t *Templ) IncreaseTemplStar(userInfo authapi.RespToken, templId string) (StarTempl, error) {
	templData, err := appTempl.GetTemplByUuid(orm.NewOrm(), templId)
	if err != nil {
		glog.Errorf("template not found for uuid, %s", templId)
		return StarTempl{}, err
	}
	if templData.Privacy != "1" {
		return StarTempl{}, fmt.Errorf("privacy template [%s/%s] can not to be star by user[%v/%v], ", templData.Name, templData.Uuid, userInfo.Name, userInfo.UserID)
	}
	var templ = dbAppTempl.Templ{
		Uuid: templId,
	}
	var starTempl = dbAppTempl.StarTempl{
		UserId:     userInfo.UserID,
		CreateTime: time.Now(),
		Uuid:       uuid.New().String(),
		Templ:      &templ,
	}
	_, err = appTempl.CreateStarTemplToUser(orm.NewOrm(), starTempl)
	if err != nil {
		glog.Errorf("create starTempl [%v] for user[%v/%v] in db failed, err: %v", starTempl.Templ.Uuid, userInfo.Name, userInfo.UserID, err)
		return StarTempl{}, err
	}
	return transformDB2APIForStarTempl(starTempl), nil
}
func (t *Templ) DecreaseStarTempl(userInfo authapi.RespToken, templId string) error {
	userId := userInfo.UserID
	templ, err := appTempl.GetStarTemplByUserIdAndTemplId(orm.NewOrm(), userId, templId)
	if err != nil {
		glog.Errorf("template %s not found By userId and userName is ,%s,%s,error is %s", templId, userId, userInfo.Name, err)
		return err
	}
	err = appTempl.DeleteStarTemplToUser(orm.NewOrm(), templ)
	return err
}

func transformDB2APIForStarTempl(t dbAppTempl.StarTempl) StarTempl {
	return StarTempl{
		Uuid:       t.Uuid,
		TemplId:    t.Templ.Uuid,
		UserId:     t.UserId,
		CreateTime: t.CreateTime,
	}
}

func transformDB2APIForTemplInStar(t dbAppTempl.StarTempl) Templ {
	return Templ{
		Uuid:          t.Templ.Uuid,
		Name:          t.Templ.Name,
		Arch:          t.Templ.Arch,
		UserId:        t.UserId,
		Template:      t.Templ.Template,
		Version:       t.Templ.Version,
		Privacy:       t.Templ.Privacy,
		Auditor:       t.Templ.Auditor,
		AuditResult:   t.Templ.AuditResult,
		AuditTime:     t.Templ.AuditTime,
		CreateTime:    t.Templ.CreateTime,
		Status:        t.Templ.Status,
		Modifier:      t.Templ.Modifier,
		ModifyTime:    t.Templ.ModifyTime,
		Desc:          t.Templ.Desc,
		Log:           t.Templ.Log,
		Kind:          t.Templ.Kind,
		DownloadCount: t.Templ.DownloadCount,
		StarCount:     t.Templ.StarCount,
		ImageUrl:      t.Templ.ImageUrl,
		Title:         t.Templ.Title,
	}
}
